今天要來介紹一個 DB 功能 seed data,有的時候當我們開發 application 時,很常會需要一些測試資料,那假設今天我們的測試資料很多,總不可能一個一個手動去新增,而且有時候也需要定期清楚過期的資料,為了簡化這些事提高開發效率,所以這時候 seed data 就派上用場了,以下我們將慢慢介紹~
seed data 大致上有以下的功能:
application 需要的測試資料migrate DB 時候 reset 你的測試資料所以 seed data 就是讓你開發時候有資料可以測試外,同時 migrate 時候也會自動重新塞新的資料,解決 schema 不一致的問題
在 prisma 中如果要使用 seed data 功能,需要在 package.json 中加上 seed 的 key 在 prisma 這個欄位,然後當你要執行 seed data 的時候你只要run prisma db seed,如此就會知道你 seed data 的檔案在哪裡
// package.json
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
如果使用 ts-node 的話要注意一件事情,就是 ts-node 他會幫你做 transpiling 跟 typechecking ,那其實 typechecking 是可以被 disabled ,只需要在你的 cli 加上 --transpile-only ,這樣 ts-node 就不會執行 typechecking ,這個參數非常好用因為他可以減少 memory (RAM) 的使用,同時加快 seed data 的執行效率
"seed": "ts-node --transpile-only prisma/seed.ts"
Datebase seeding 在 prisma 中有兩種方式 :
prisma db seed 手動載入prisma migrate reset 自動執行 seed data 或是 prisma migrate dev (某些情況)使用 prisma db seed 手動方式,他的好處在於你可以測試 seed data 是否正確同時也會是你在開發前的前置作業需要確認的事情
另外當你 prisma db seed 都沒問題的時候,就可以透過 prisma migrate reset 自動執行,他會有以下的步驟:
prisma migrate reset 這個 cli
database 會執行 prisma migrate dev 去跟去你 migration 的記錄解決 schema 的衝突prisma db seed
另外假設你不需要 seed data 在你執行,在 prisma migrate reset 或是 prisma migrate dev 只要加上 skip-seed 這個 flag 就好
以下是今天的 model
model User {
id String @id @default(cuid())
name String
age Int?
profileViews Int
country String
city String
email String @unique
}
之後我們在 prisma 這個資料夾新增 seed.ts 的檔案,這邊簡單解釋一下 seed.ts 做了什麼事情:
@faker-js 產生 mock data
prisma.user.deleteMany 每次清空 seed data
prisma.user.upsert 當 email 有存在就跳過 craete ,確保 seed data 過程中 email 可能會有重複,原因是我們的 email 的 schema 是 unique 的//prisma/seed.ts
import { faker } from "@faker-js/faker";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient()
const main = async () => {
await prisma.user.deleteMany({})
for (let i = 0; i < 20; i++) {
const userEmail = faker.internet.email()
await prisma.user.upsert({
where: {
email: userEmail
},
update: {},
create: {
email: userEmail,
name: faker.person.fullName(),
age: faker.helpers.maybe(() => faker.number.int({ min: 0, max: 100 }), { probability: 0.8 }),
profileViews: faker.number.int({ min: 0, max: 5000 }),
country: faker.location.country(),
city: faker.location.city()
}
})
}
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
那因為在 age 我們的 schema 是 optional 的,所以為了讓 seed 的更真實,@faker-js 有提供 maybe 的用法,有機率的 return data 用這個 utils 來模擬 user 沒有 age 的資料
所以以下的 code 會是有八成的機率 age 會有資料,否則會是 null
//..
age: faker.helpers.maybe(() => faker.number.int({ min: 0, max: 100 }), { probability: 0.8 })
//..
之後到 package.json 加上 prisma script
//package.json
{
"name": "my-project",
"version": "1.0.0",
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"devDependencies": {
"@types/node": "^14.14.21",
"ts-node": "^9.1.1",
"typescript": "^4.1.3"
}
}
執行 seed script
>npx prisma db seed
接著我們到 prisma studio 檢查一下確實 age 有些資料是 null 的

另外 ts-node 有提供不同 compiler 的選項,如果你是使用 Nextjs 的話需要根據以下的寫法去轉換一下 module
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},
其實在 prisma 中你還可以透過 Raw SQL 幫你 seed data ,使用 prisma.$executeRaw 就可以幫你完成
async function rawSql() {
const result = await prisma.$executeRaw`INSERT INTO "User" ("id", "email", "name") VALUES (3, 'foo@example.com', 'Foo') ON CONFLICT DO NOTHING;`
console.log({ result })
}
這邊你可以透過 Raw SQL 幫你新增額外的 seed data
main()
.then(rawSql)
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
另外還有一個今天的主角 @snaplet/seed 他也是另外一套 seed data 的 toolkit 他的好處是可以自動幫你提供 seed data 的 type 外,同時也幫你優化 seed data 的效率,然後 @snaplet/seed 目前支援 PostgreSQL 、SQLite and MySQL
使用前要先 init @snaplet/seed
>npx @snaplet/seed init prisma/seed
他會幫你在 prisma 資料夾中產生以下的檔案:
seed.config.ts : seed 相關的 config
seed.ts : 執行 seed 的位置
我們可以看到 seed.config.ts 主要就是透過 adapter 去 connect PrismaClient
//seed.config.ts
import { SeedPrisma } from "@snaplet/seed/adapter-prisma";
import { defineConfig } from "@snaplet/seed/config";
import { PrismaClient } from "@prisma/client";
export default defineConfig({
adapter: () => {
const client = new PrismaClient();
return new SeedPrisma(client);
},
select: ["!*_prisma_migrations"],
});
@snaplet/seed 的寫法很單純,他會自動幫你判別你的 model 需要什麼欄位,自動幫你填上,這邊簡單解釋一下 code :
$resetDatabase 每次都清空 DB
await seed.user((createMany) => createMany(10)) 創建10筆 user 的資料// import { createSeedClient, SeedClient } from "@snaplet/seed";
const main = async () => {
// Truncate all tables in the database
await seed.$resetDatabase();
await seed.user((createMany) => createMany(10));
// Type completion not working? You might want to reload your TypeScript Server to pick up the changes
console.log(`Database seeded successfully!`);
process.exit();
};
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
這邊讀者打算把 ts-node 改成 tsx ,因為 tsx 他可以讓你在執行時候不用考慮 module 問題~
>npm install -D tsx
最後別忘記到 package.json 中修改一下 prisma 的 script
//package.json
{
"name": "my-project",
"version": "1.0.0",
"prisma": {
"seed": "tsx prisma/seed/seed.ts"
},
"devDependencies": {
"@types/node": "^14.14.21",
"tsx": "^4.7.2",
"typescript": "^4.1.3"
}
}
執行 seed data
>npx prisma db seed
但是筆者在查看 studio 後,發現資料內容,只是單純的塞假文字進去,這樣的 seed data 沒有還原真實資料的情況

目前筆者找到解決方式有兩種,一種是塞 env ,因為 @snaplet/seed 有支援 openai,透過 openai 優化你 response 部分
//.env
OPENAI_API_KEY="your_token"
另外一種就是透過 @faker-js ,這邊我們寫一個 seedData function ,然後你會發現我們是透過迴圈方式,去 createMany 一筆資料,那是因為假如是 createMany(100) ,對 @snaplet/seed 來說他實際並不是跑 100 次的 query 而是只有一次
const seedData = async ({
seed,
count
}: {
seed: SeedClient,
count: number
}) => {
for (let i = 0; i < count; i++) {
await seed.user((createMany) => createMany(1, {
name: faker.person.fullName(),
age: faker.number.int({ min: 0, max: 100 }),
profileViews: faker.number.int({ min: 0, max: 5000 }),
country: faker.location.country(),
city: faker.location.city(),
email: faker.internet.email()
}));
}
}
那這樣透過 faker 出來的資料因為執行次數的關係,資料都是一樣的,所以筆者才會改成遞迴方式,而且這樣的 email 也會有問題就不會是唯一性了
await seed.user((createMany) => createMany(100, {
name: faker.person.fullName(),
age: faker.number.int({ min: 0, max: 100 }),
profileViews: faker.number.int({ min: 0, max: 5000 }),
country: faker.location.country(),
city: faker.location.city(),
email: faker.internet.email()
}));

完整的 code 如下
const main = async () => {
const seed = await createSeedClient();
// Truncate all tables in the database
await seed.$resetDatabase();
await seedData({ seed, count: 20 })
// Type completion not working? You might want to reload your TypeScript Server to pick up the changes
console.log(`Database seeded successfully!`);
process.exit();
};
另外如果你想自動執行 seed 的話,只需要在 package.json 的 script 加上 migrate 跟 postmigrate
//package.json
{
"name": "my-project",
"version": "1.0.0",
"prisma": {
"seed": "npx tsx prisma/seed/seed.ts"
},
"scripts": {
"migrate": "prisma migrate dev",
"postmigrate": "npx @snaplet/seed sync"
},
"devDependencies": {
"@types/node": "^14.14.21",
"tsx": "^4.7.2",
"typescript": "^4.1.3"
}
}
這樣當你 npm run migrate 就會幫你自動 seed data 了
>npm run migrate
> prisma@1.0.0 migrate
> prisma migrate dev
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
Already in sync, no schema change or pending migration was found.
✔ Generated Prisma Client (v5.19.1) to ./node_modules/@prisma/client in 54ms
> prisma@1.0.0 postmigrate
> npx @snaplet/seed sync
最後補充一個有趣的小 tips 我們其實可以根據下不同的 flag 在執行 prisma db seed 的時候,有不同的 seed data 的結果,只需要用 nodejs 原生的parseArgs 去讀取 cli 的參數,以下的範例就是區分 environment 有 development 跟 test,development 有20筆資料 test 則是只有10筆,那讀者可以根據不同情況調整需求~
import { faker } from "@faker-js/faker";
import { PrismaClient } from "@prisma/client";
import { createSeedClient, SeedClient } from "@snaplet/seed";
import { parseArgs, ParseArgsConfig } from "util";
const main = async () => {
const {
values: { environment },
} = parseArgs({ options })
const seed = await createSeedClient();
// Truncate all tables in the database
await seed.$resetDatabase();
switch (environment) {
case 'development':
seedData({ seed, count: 20 })
break
case 'test':
seedData({ seed, count: 10 })
break
default:
break
}
// Type completion not working? You might want to reload your TypeScript Server to pick up the changes
console.log(`environment(${environment}): Database seeded successfully!`);
process.exit();
};
最後只要下以下的 cli 就成功了~
>npx prisma db seed -- --environment development
✅ 前端社群 :
https://lihi3.cc/kBe0Y